On this page

Skip to content

A Brief Discussion on the Dispose Pattern and the using Statement

TLDR

  • Unmanaged resources (such as database connections and files) must be released manually; the Garbage Collector (GC) cannot handle them automatically.
  • Implementing the IDisposable interface and releasing resources in the Dispose() method is the standard practice.
  • When implementing IDisposable, prioritize Dispose and use GC.SuppressFinalize(this) to prevent the GC from redundantly calling the finalizer.
  • If an object implements both IDisposable and IAsyncDisposable, it is recommended to implement both to ensure compatibility, noting that DisposeAsync() takes precedence in ASP.NET Core.
  • The using statement is essentially syntactic sugar for try...finally, ensuring that resources are correctly released when leaving the scope.
  • C# 8.0 introduced a more concise using declaration syntax, which automatically releases resources at the end of the scope without requiring additional nested braces.

Dispose Pattern

Handling Unmanaged Resources

When to encounter this issue: When the program needs to operate on unmanaged resources such as database connections, file access, or native operating system handles.

Since the .NET Garbage Collector (GC) only manages managed memory and cannot automatically reclaim unmanaged resources, developers must manually release them using the following methods:

  • Implement the IDisposable interface: Release resources in the Dispose() method.
  • Declare a finalizer: Acts as a last line of defense, called automatically when the GC reclaims the object.

Implementation Example

The following is the standard implementation of the Dispose pattern, including a mechanism to prevent redundant disposal:

csharp
public class ResourceHandle : IDisposable {
    private bool disposed = false;
    private IntPtr unmanagedResource;
    private ManagedResource managedResource;

    public ResourceHandle() {
        managedResource = new ManagedResource();
    }

    public void Dispose() {
        Dispose(true);
        // Inform the GC that this object has been manually disposed, no need to call the finalizer
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing) {
        if (!disposed) {
            if (disposing) {
                managedResource?.Dispose();
                managedResource = null;
            }

            // Release unmanaged resources
            if (unmanagedResource != IntPtr.Zero) {
                FreeUnmanagedResource(unmanagedResource);
                unmanagedResource = IntPtr.Zero;
            }
            disposed = true;
        }
    }

    ~ResourceHandle() {
        Dispose(false);
    }
}

Asynchronous Dispose Pattern

When to encounter this issue: When the resource release process involves asynchronous I/O operations (such as asynchronous log writing or closing network connections).

Recommendations for implementing IAsyncDisposable:

  • Implement both IDisposable and IAsyncDisposable: Ensures that resources can still be correctly released in legacy code that does not support asynchronous disposal.
  • DisposeAsync() takes precedence: In the ASP.NET Core dependency injection container, if an object implements both, DisposeAsync() will be called first.
  • Avoid deadlocks: Do not call asynchronous methods within Dispose(bool disposing); handle only synchronous release logic there.

using Statement

Ensuring Resource Release

When to encounter this issue: When you need to ensure that an object correctly releases resources even when an exception occurs, preventing memory leaks or connection exhaustion.

The using statement is compiled into a try...finally structure, ensuring that the Dispose() method is always executed.

csharp
// Traditional using syntax
using (ResourceHandle handle = new ResourceHandle()) {
    // Execute business logic
}

// C# 8.0 concise syntax
{
    using ResourceHandle handle = new ResourceHandle();
    // Dispose() is called automatically when leaving the scope
}

Nested and Asynchronous Handling

For the release of multiple resources, nested structures can be simplified:

csharp
// Combined declaration
using (ResourceHandle h1 = new ResourceHandle(), h2 = new ResourceHandle()) {
    // Execute logic
}

// Asynchronous disposal
await using (AsyncDisposableObject resource = new AsyncDisposableObject()) {
    // Execute asynchronous logic
}

Change Log

  • 2024-08-08 Initial document creation.